"""
resampling.py
Jose Guzman, sjm.guzman@gmail.com
Date: Fri Mar  7 17:47:25 CET 2014

This file contains different classes for resampling 
techniques of different probabilistic models
"""

from numpy import sum
import networkx as nx

class MultipleRecordingSimulator(object):
    """
    A simulation class that contains the different recording configurations
    and simulates an equal number and type of random graphs 
    assuming that the probabily of edge creation is the same as
    the empirical probability found in the recording configuration.
    """
    def __init__(self, conf, seed = None):
        """
        MultipleRecordingSimulator object 

        Parameters
        ----------

        conf : dict
               a dictionary of dictionaries containing the 
               different recording configurations (e.g pairs, triplets,)
               together with a number of configurations tested 'nconf' 
               and number of connections found 'nfound' as keys for 
               every configuration. For example:
               conf['pairs'] = {'nconf' : 495 , 'nfound': 5}
        seed : int, optional
               Seed for random number generator (defaul=None) 
        """
        
        self._conf = conf
        self.seed = seed 

        self.unidirectional  = 0
        self.bidirectional   = 0
        self.unconnected = 0

        # calculate the empirical connection probability 
        # based on the number of connections found from the
        # all possible tested connections
        self._nconnections =  \
            conf['pairs']['nconf']*2 + \
            conf['triples']['nconf']*6 + \
            conf['quadruples']['nconf']*12 + \
            conf['quintuples']['nconf']*20 + \
            conf['sextuples']['nconf']*30 + \
            conf['septuples']['nconf']*42 + \
            conf['octuples']['nconf']*56
        self._nfound = sum( [c['nfound'] for c in conf.values()] )

        self._probability = float(self._nfound)/ self._nconnections 

    def _random_graph(self, N, nodes):
        """
        simulates N Erdos-Renyi random graphs G(n, p) with n number of 
        nodes and probability p from the empirical connection probabilty. 
        Parameters
        ----------
        N : int 
            the number of times that the graph is created (number of
            simulated graphs). This is the number of recordings with
            a given configuration (e.g 30 octuple configurations).
        nodes: int
               number of nodes of the graph (number of the cells for
               a configuration (e.g 8 cells in an octuple recording).
        
        will update the number of unconnected, unidirectional and bidirectional 
        connected edges!!!.
        """
        
        for _ in xrange(N):
            G = nx.fast_gnp_random_graph(n = nodes, p = self.p, \
                seed = self.seed, directed = True)
            # total number of edges in the graph
            self.unidirectional +=  G.size() 
            # number of bidirectional edges in the graph 
            self.bidirectional += int(0.5 * \
                len([1 for (u,v) in G.edges() if u in G[v]]) )
            # number of unconnected 
            self.unconnected += (nodes*(nodes-1) - G.size())
            

    def simulate(self):
        """
        Simulate as many random graphs as recording configurations 
        and returns the number of unidirectional connections
        """
        # set random connections to zero
        self.unidirectional  = 0
        self.bidirectional   = 0
        self.unconnected = 0

        self._random_graph( self._conf['pairs']['nconf'], 2 )
        self._random_graph( self._conf['triples']['nconf'], 3 )
        self._random_graph( self._conf['quadruples']['nconf'], 4 )
        self._random_graph( self._conf['quintuples']['nconf'], 5 )
        self._random_graph( self._conf['sextuples']['nconf'], 6 )
        self._random_graph( self._conf['septuples']['nconf'], 7 )
        self._random_graph( self._conf['octuples']['nconf'], 8 )
        

    # getter-only methods for empirical measurements
    p = property(lambda self: self._probability)
    nfound = property(lambda self: self._nfound)
    nconnections = property(lambda self: self._nconnections)
        
            
if __name__ == '__main__':
    pass
